이번 튜토리얼에서는 텍스트 파일에서 데이터를 불러오기 위한 함수인 tf.data.TextLineDataset
를 사용해 텍스트 데이터를 다중 분류하는 방법을 알아볼 것입니다.
TextLineDataset
은 텍스트 파일에서 데이터셋을 생성해 각 예제가 원본 파일에서 텍스트 줄처럼 배열되도록 설계되었습니다. 이는 주로 시나 오류 로그와 같은 줄 기반의 텍스트 데이터에 유용하게 사용됩니다.
이번 튜토리얼에서는 호머의 일리아드의 같은 부분에 대한 세 가지의 다른 영어 번역문을 학습해서, 한 줄의 텍스트로 번역가를 구분하는 모델을 만들 것입니다.
import warnings
warnings.simplefilter('ignore')
import tensorflow as tf
import tensorflow_datasets as tfds
import os
번역문은 아래와 같은 세 가지 문헌을 사용합니다:
이번 튜토리얼에서 사용된 텍스트 파일은 문서 머리글과 바닥글, 줄 번호, 챕터 제목을 제거하는 것과 같은 몇 가지 일반적인 전처리 작업이 이미 적용되어있습니다.
3개의 텍스트 파일을 다운로드 받겠습니다.
DIRECTORY_URL = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
FILE_NAMES = ['cowper.txt', 'derby.txt', 'butler.txt']
for name in FILE_NAMES:
text_dir = tf.keras.utils.get_file(name, origin=DIRECTORY_URL+name)
parent_dir = os.path.dirname(text_dir)
parent_dir
각 텍스트 데이터를 데이터셋으로 불러옵니다.
샘플마다 레이블(label)이 필요하므로 각 샘플과 레이블의 매칭을 위하여, tf.data.Dataset.map
를 사용합니다.
그러면 데이터셋 내의 모든 샘플에 대한 (example, label
)쌍이 반환될 것입니다.
def labeler(example, index):
return example, tf.cast(index, tf.int64)
labeled_data_sets = []
for i, file_name in enumerate(FILE_NAMES):
lines_dataset = tf.data.TextLineDataset(os.path.join(parent_dir, file_name))
labeled_dataset = lines_dataset.map(lambda ex: labeler(ex, i))
labeled_data_sets.append(labeled_dataset)
이렇게 레이블이 표시된 데이터셋을 단일 데이터셋에 결합하고 섞어보겠습니다.
BUFFER_SIZE = 50000
BATCH_SIZE = 64
TAKE_SIZE = 5000
all_labeled_data = labeled_data_sets[0]
for labeled_dataset in labeled_data_sets[1:]:
all_labeled_data = all_labeled_data.concatenate(labeled_dataset)
all_labeled_data = all_labeled_data.shuffle(BUFFER_SIZE, reshuffle_each_iteration=False)
tf.data.Dataset.take
과 print
를 이용해 (example, label)
쌍의 내용을 확인할 수 있습니다. numpy
는 각 텐서의 값을 보여줍니다.
for ex in all_labeled_data.take(5):
print(ex)
머신러닝 모델은 단어가 아닌 숫자로 학습하기 때문에 문자열 값을 숫자의 목록으로 변환해야 합니다. 따라서 각 고유 단어를 고유한 정수로 매핑하는 과정이 필요합니다.
먼저 텍스트를 개별 고유 단어의 집합으로 토큰화하여 어휘를 만듭니다. TensorFlow와 Python 모두 토큰화를 할 수 있는 몇 가지 방법이 있습니다. 이 튜토리얼의 경우에는 아래와 같은 방법을 사용합니다.
numpy
값을 얻습니다.tfds.features.text.Tokenizer
를 사용합니다.tokenizer = tfds.features.text.Tokenizer()
vocabulary_set = set()
for text_tensor, _ in all_labeled_data:
some_tokens = tokenizer.tokenize(text_tensor.numpy())
vocabulary_set.update(some_tokens)
vocab_size = len(vocabulary_set)
vocab_size
vocabulary_set
을 tfds.features.text.TokenTextEncoder
에 전달하여 인코더를 만듭니다. 인코더의 encode
메서드는 문자열을 받아 정수 리스트를 반환합니다.
encoder = tfds.features.text.TokenTextEncoder(vocabulary_set)
하나의 샘플을 이용해 어떻게 출력이 되는지 확인해보겠습니다.
example_text = next(iter(all_labeled_data))[0].numpy()
print(example_text)
encoded_example = encoder.encode(example_text)
print(encoded_example)
이제 tf.py_function
으로 변환하고 데이터셋의 map
메서드에 전달하는 방식으로 인코더를 실행해봅시다.
def encode(text_tensor, label):
encoded_text = encoder.encode(text_tensor.numpy())
return encoded_text, label
이를 데이터셋의 각 요소에 적용하기 위해서 Dataset.map
을 사용합니다. Dataset.map
은 그래프 모드로 실행됩니다.
그래서 위의 함수를 직접 ‘.map’할 수 없으며 tf.py_function
으로 변환해야 합니다. tf.py_function
은 python 함수에 일반 텐서(값과 그 값을 확인하기 위한 .numpy()
메서드를 포함)를 전달합니다.
def encode_map_fn(text, label):
encoded_text, label = tf.py_function(encode,
inp=[text, label],
Tout=(tf.int64, tf.int64))
encoded_text.set_shape([None])
label.set_shape([])
return encoded_text, label
all_encoded_data = all_labeled_data.map(encode_map_fn)
작은 테스트 데이터셋과 그보다 큰 훈련 데이터셋을 만들기 위해 tf.data.Dataset.take
와 tf.data.Dataset.skip
를 사용할 것입니다.
모델에 학습시키기 전에 먼저 데이터셋을 배치(batch)로 만들어야 합니다. 일반적으로 배치 내부의 샘플은 동일한 크기와 모양이 되어야 하지만 현재 데이터셋은 텍스트마다 단어의 수가 다르게 구성되는 등 샘플들의 크기가 모두 같지는 않습니다. 따라서 이를 동일한 크기의 샘플들로 패딩하기 위해 배치(batch)가 아닌 tf.data.Dataset.padded_batch
를 사용합니다.
train_data = all_encoded_data.skip(TAKE_SIZE).shuffle(BUFFER_SIZE)
train_data = train_data.padded_batch(BATCH_SIZE, ((None,), ()))
test_data = all_encoded_data.take(TAKE_SIZE)
test_data = test_data.padded_batch(BATCH_SIZE, ((None,), ()))
이제 test_data
와 train_data
는 (example, label
)쌍이 아닌 배치(batch) 형태로 모인 것입니다. 또한 각 배치는 (여러 샘플, 여러 레이블) 쌍이 배열로 구성되어 있습니다.
그 예시는 다음과 같습니다:
sample_text, sample_labels = next(iter(test_data))
sample_text[0], sample_labels[0]
새로운 토큰 인코딩(패딩에 쓰이는 0)을 사용했기 때문에 어휘 사전의 크기가 1개 더 늘어났을 것입니다.
vocab_size += 1
model = tf.keras.Sequential()
첫 번째 레이어는 정수형으로 인코딩된 데이터를 밀도 있는 벡터 임베딩(dense vector embeddings)으로 변환합니다.
model.add(tf.keras.layers.Embedding(vocab_size, 64))
다음 레이어는 LSTM(Long Short-Term Memory, LSTM) 계층으로, 해당 계층은 모델들이 다른 단어들과의 맥락에서 단어들을 이해할 수 있도록 해줍니다. LSTM의 양방향 래퍼(wrapper)는 LSTM이 데이터 포인트 이전 및 이후와 관련된 데이터 포인트를 학습하는 데 도움을 줍니다.
model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)))
마지막으로 하나 이상의 연결된 계층(densly connected layers)과 마지막 계층인 출력 계층(output layer)입니다. 출력 레이어는 모든 레이블에 대한 확률을 생성합니다. 가장 높은 확률을 가진 것이 모델이 샘플 레이블에 대해서 예측한 예측 값입니다.
#`for`문 안의 list를 내용을 수정하면 레이어의 크기에 따른 변화를 볼 수 있습니다.
for units in [64, 64]:
model.add(tf.keras.layers.Dense(units, activation='relu'))
# 출력 레이어입니다. 첫 번째 인자는 최종적으로 출력해야 하는 레이블의 개수입니다.
model.add(tf.keras.layers.Dense(3, activation='softmax'))
마지막으로 모델을 컴파일합니다. 소프트맥스(softmax) 분류 모델의 경우 손실 함수(loss function)로 sparse_categorical_crossentropy
를 사용합니다. 다른 최적화 도구도 사용해 볼 수 있지만 그 중 가장 많이 사용되는 것은 adam
옵티마이저 입니다.
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
이 데이터로 실행되는 이 모델은 약 83%로 나쁘지 않은 결과를 도출합니다
model.fit(train_data, epochs=3, validation_data=test_data)
eval_loss, eval_acc = model.evaluate(test_data)
print('\n검증 손실: {:.3f}, 검증 정확도: {:.3f}'.format(eval_loss, eval_acc))
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.